1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.graphics.g2d.geometrybatch; 12 import hip.graphics.orthocamera; 13 import hip.hiprenderer.renderer; 14 import hip.hiprenderer.shader; 15 import hip.error.handler; 16 import hip.graphics.mesh; 17 import hip.math.matrix; 18 import hip.math.utils; 19 import hip.math.vector; 20 public import hip.api.graphics.color; 21 public import hip.api.graphics.batch; 22 23 24 enum defaultColor = HipColor.white; 25 26 @HipShaderInputLayout struct HipGeometryBatchVertex 27 { 28 import hip.math.vector; 29 Vector3 vPosition; 30 // @HipShaderInputPadding float __padding = 0; 31 HipColor vColor = HipColor.white; 32 33 static enum floatCount = HipGeometryBatchVertex.sizeof / float.sizeof; 34 } 35 36 @HipShaderVertexUniform("Geom") 37 struct HipGeometryBatchVertexUniforms 38 { 39 Matrix4 uMVP = Matrix4.identity; 40 } 41 42 @HipShaderFragmentUniform("FragVars") 43 struct HipGeometryBatchFragmentUniforms 44 { 45 float[4] uGlobalColor = [1,1,1,1]; 46 } 47 48 /** 49 * This class uses the vertex layout XYZ RGBA. 50 * it is meant to be a 2D API for drawing primitives 51 */ 52 class GeometryBatch : IHipBatch 53 { 54 protected Mesh mesh; 55 protected index_t lastIndexDrawn; 56 protected index_t lastVertexDrawn; 57 protected index_t currentIndex; 58 protected index_t verticesCount; 59 protected index_t indicesCount; 60 protected HipColor currentColor; 61 62 float managedDepth = 0; 63 HipOrthoCamera camera; 64 HipGeometryBatchVertex[] vertices; 65 index_t[] indices; 66 67 68 this(HipOrthoCamera camera = null, index_t verticesCount=DefaultMaxGeometryBatchVertices, index_t indicesCount=DefaultMaxGeometryBatchVertices) 69 { 70 import hip.hiprenderer.initializer; 71 Shader s = newShader(HipShaderPresets.GEOMETRY_BATCH); 72 s.addVarLayout(ShaderVariablesLayout.from!(HipGeometryBatchVertexUniforms)(HipRenderer.getInfo)); 73 s.addVarLayout(ShaderVariablesLayout.from!(HipGeometryBatchFragmentUniforms)(HipRenderer.getInfo)); 74 s.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD); 75 76 77 mesh = new Mesh(HipVertexArrayObject.getVAO!HipGeometryBatchVertex, s); 78 vertices = new HipGeometryBatchVertex[verticesCount]; 79 indices = new index_t[indicesCount]; 80 mesh.createVertexBuffer(verticesCount, HipResourceUsage.Dynamic); 81 mesh.createIndexBuffer(indicesCount, HipResourceUsage.Dynamic); 82 mesh.setIndices(indices); 83 mesh.setVertices(vertices); 84 mesh.sendAttributes(); 85 this.setColor(defaultColor); 86 87 if(camera is null) 88 camera = new HipOrthoCamera(); 89 this.camera = camera; 90 91 } 92 93 protected pragma(inline) void checkVerticesCount(int howMuch) 94 { 95 if(verticesCount+howMuch >= this.vertices.length/HipGeometryBatchVertex.floatCount) 96 flush(); 97 } 98 99 void setCurrentDepth(float depth){managedDepth = depth;} 100 101 102 /** 103 * Adds a vertex to the structure and return its current index. 104 */ 105 index_t addVertex(float x, float y, float z) 106 { 107 if(currentColor.a == 0) return verticesCount; 108 vertices[verticesCount] = HipGeometryBatchVertex( 109 Vector3(x,y,z), 110 currentColor 111 ); 112 return verticesCount++; 113 } 114 115 void addIndex(index_t[] newIndices ...) 116 { 117 if(currentColor.a == 0) return; 118 if(currentIndex+newIndices.length >= this.indices.length) 119 { 120 import hip.util.string; 121 String s = String("Too many indices ", currentIndex+1, " for a buffer of size ", this.indices.length); 122 ErrorHandler.assertExit(false, s.toString); 123 } 124 foreach(index; newIndices) 125 indices[currentIndex++] = index; 126 } 127 void setColor(HipColor c) 128 { 129 assert(c != HipColor.no, "Can't use 'no' color on geometry batch"); 130 currentColor = c; 131 } 132 133 protected void triangleVertices(int x1, int y1, int x2, int y2, int x3, int y3) 134 { 135 checkVerticesCount(3); 136 addVertex(x1, y1, managedDepth); 137 addVertex(x2, y2, managedDepth); 138 addVertex(x3, y3, managedDepth); 139 addIndex( 140 cast(index_t)(verticesCount-3), 141 cast(index_t)(verticesCount-2), 142 cast(index_t)(verticesCount-1) 143 ); 144 } 145 146 147 protected void fillEllipseVertices(int x, int y, int radiusW, int radiusH, int degrees, int startDegrees ,int precision) 148 { 149 // assert(precision >= 3, "Can't have a circle with less than 3 vertices"); 150 151 //Normalize the precision for iterating it on the loop, 152 //Multiply by degrees * DEG_TO_RAD 153 float angle_mult = (1.0/precision) * (degrees) * (PI/180.0); 154 155 float startAngle = (PI/180.0) * startDegrees; 156 157 checkVerticesCount(2); 158 index_t centerIndex = addVertex(x, y, managedDepth); 159 //The first vertex 160 index_t lastVert = addVertex(x + radiusW*cos(startAngle), y + radiusH*sin(startAngle), managedDepth); 161 index_t firstVert = lastVert; 162 163 checkVerticesCount(precision); 164 for(int i = 0; i < precision; i++) 165 { 166 //Divide degrees for the total iterations 167 float nextAngle = (i+1)*angle_mult + startAngle; 168 169 //Use a temporary variable to hold the new lastVert for more performance 170 //on addIndex calls 171 index_t tempNewLastVert = addVertex(x+radiusW*cos(nextAngle), y + radiusH*sin(nextAngle), managedDepth); 172 173 addIndex( 174 centerIndex, //Puts the center first 175 lastVert, //Appends the vertex from the last iteration 176 tempNewLastVert//Appends the next vertex 177 ); 178 //Updates the last iteration with the next vertex 179 lastVert = tempNewLastVert; 180 } 181 182 addIndex( 183 centerIndex, 184 lastVert, 185 firstVert 186 ); 187 } 188 189 190 191 void drawEllipse(int x, int y, int radiusW, int radiusH, int degrees = 360, HipColor color = HipColor.no, int precision = 24) 192 { 193 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 194 if(HipRenderer.getMode != HipRendererMode.line) 195 { 196 flush(); 197 HipRenderer.setRendererMode(HipRendererMode.line); 198 } 199 float angle_mult = (1.0/precision) * degrees * (PI/180.0); 200 checkVerticesCount(1); 201 index_t currVert = addVertex(x+ radiusW*cos(0.0), y + radiusH*sin(0.0), managedDepth); 202 index_t firstVert = currVert; 203 204 checkVerticesCount(precision); 205 for(int i = 1; i < precision+1; i++) 206 { 207 float nextAngle = angle_mult * i; 208 index_t tempNextVert = addVertex(x + radiusW * cos(nextAngle), y + radiusH*sin(nextAngle), managedDepth); 209 210 addIndex(currVert, tempNextVert); 211 currVert = tempNextVert; 212 } 213 214 addIndex(firstVert, currVert); 215 setColor(oldColor); 216 } 217 218 private HipColor setColorIfChangedAndGetOldColor(in HipColor color) 219 { 220 HipColor oldColor = currentColor; 221 if(color != HipColor.no) 222 setColor(color); 223 return oldColor; 224 } 225 226 ///With this default precision, the circle should be smooth enough 227 void fillEllipse(int x, int y, int radiusW, int radiusH = -1, int degrees = 360, HipColor color = HipColor.no, int precision = 24) 228 { 229 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 230 if(radiusH == -1) 231 radiusH = radiusW; 232 if(HipRenderer.getMode != HipRendererMode.triangles) 233 { 234 flush(); 235 HipRenderer.setRendererMode(HipRendererMode.triangles); 236 } 237 fillEllipseVertices(x, y, radiusW, radiusH, degrees, 0, precision); 238 setColor(oldColor); 239 } 240 241 void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, HipColor color = HipColor.no) 242 { 243 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 244 if(HipRenderer.getMode != HipRendererMode.triangles) 245 { 246 flush(); 247 HipRenderer.setRendererMode(HipRendererMode.triangles); 248 } 249 triangleVertices(x1,y1,x2,y2,x3,y3); 250 setColor(oldColor); 251 } 252 void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, HipColor color = HipColor.no) 253 { 254 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 255 if(HipRenderer.getMode != HipRendererMode.lineStrip) 256 { 257 flush(); 258 HipRenderer.setRendererMode(HipRendererMode.lineStrip); 259 } 260 triangleVertices(x1, y1, x2, y2, x3, y3); 261 setColor(oldColor); 262 } 263 264 void drawLine(int x1, int y1, int x2, int y2, HipColor color = HipColor.no) 265 { 266 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 267 if(HipRenderer.getMode != HipRendererMode.line) 268 { 269 flush(); 270 HipRenderer.setRendererMode(HipRendererMode.line); 271 } 272 checkVerticesCount(2); 273 addVertex(x1, y1, managedDepth); 274 addVertex(x2, y2, managedDepth); 275 276 addIndex( 277 cast(index_t)(verticesCount-2), 278 cast(index_t)(verticesCount-1) 279 ); 280 setColor(oldColor); 281 } 282 283 void drawLine(float x1, float y1, float x2, float y2, HipColor color = HipColor.no) 284 { 285 drawLine( 286 cast(int)x1, 287 cast(int)y1, 288 cast(int)x2, 289 cast(int)y2, 290 color 291 ); 292 } 293 294 void drawQuadraticBezierLine(int x0, int y0, int x1, int y1, int x2, int y2, int precision=24, HipColor color = HipColor.no) 295 { 296 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 297 298 Vector2 last = Vector2(x0, y0); 299 300 float precisionMultiplier = 1.0f/precision; 301 302 for(int i = 0; i <= precision; i++) 303 { 304 float t = cast(float)i*precisionMultiplier; 305 float tNext = t+precisionMultiplier; 306 Vector2 bz = quadraticBezier(x0, y0, x1, y1, x2, y2, tNext); 307 drawLine(last.x, last.y, bz.x, bz.y); 308 last = bz; 309 } 310 drawLine(last.x, last.y, x2, y2); 311 setColor(oldColor); 312 } 313 314 void drawPixel(int x, int y, HipColor color = HipColor.no) 315 { 316 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 317 if(HipRenderer.getMode != HipRendererMode.point) 318 { 319 flush(); 320 HipRenderer.setRendererMode(HipRendererMode.point); 321 } 322 checkVerticesCount(1); 323 addVertex(x, y, managedDepth); 324 addIndex(verticesCount); 325 setColor(oldColor); 326 } 327 328 /** 329 * Draws the following rectangle scheme: 330 * 0 _______ 3 331 * | | 332 * | | 333 * |_______| 334 * 1 2 335 * 0, 1, 2 336 * 2, 3, 0 337 */ 338 pragma(inline, true) 339 protected void rectangleVertices(int x, int y, int w, int h) 340 { 341 checkVerticesCount(4); 342 index_t topLeft = addVertex(x, y, managedDepth); 343 index_t botLeft = addVertex(x, y+h, managedDepth); 344 index_t botRight= addVertex(x+w, y+h, managedDepth); 345 index_t topRight= addVertex(x+w, y, managedDepth); 346 347 addIndex( 348 topLeft, botLeft, botRight, 349 botRight, topRight, topLeft 350 ); 351 352 } 353 354 void drawRectangle(int x, int y, int w, int h, HipColor color = HipColor.no) 355 { 356 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 357 if(HipRenderer.getMode != HipRendererMode.lineStrip) 358 { 359 flush(); 360 HipRenderer.setRendererMode(HipRendererMode.lineStrip); 361 } 362 rectangleVertices(x,y,w,h); 363 setColor(oldColor); 364 } 365 366 void fillRoundRect(int x, int y, int w, int h, int radius = 4, HipColor color = HipColor.no, int vertices = 16) 367 { 368 if(radius == 0) 369 return fillRectangle(x,y,w,h,color); 370 if(HipRenderer.getMode != HipRendererMode.triangles) 371 { 372 flush(); 373 HipRenderer.setRendererMode(HipRendererMode.triangles); 374 } 375 int vPerEdge = vertices/4; 376 int r2 = radius*2; 377 HipColor old = setColorIfChangedAndGetOldColor(color); 378 379 ///Draw internal rect. 380 rectangleVertices(x+radius, y+radius, w-r2, h-r2); 381 382 ///Draw ellipses and also draw border rects 383 //Top Left 384 fillEllipseVertices(x+radius, y+radius, radius, radius, 90, 180, vPerEdge); 385 rectangleVertices(x+radius, y, w - r2, radius); 386 //Top Right 387 fillEllipseVertices(x+w-radius, y+radius, radius, radius, 90, 270, vPerEdge); 388 rectangleVertices(x+w-radius, y+radius, radius, h-r2); 389 // Bottom Right 390 fillEllipseVertices(x+w-radius, y+h-radius, radius, radius, 90, 0, vPerEdge); 391 rectangleVertices(x+radius, y+h-radius, w-r2, radius); 392 //Bottom Left 393 fillEllipseVertices(x+radius, y+h-radius, radius, radius, 90, 90, vPerEdge); 394 rectangleVertices(x, y+radius, radius, h-r2); 395 396 setColor(old); 397 } 398 399 400 void fillRectangle(int x, int y, int w, int h, HipColor color = HipColor.no) 401 { 402 HipColor oldColor = setColorIfChangedAndGetOldColor(color); 403 if(HipRenderer.getMode != HipRendererMode.triangles) 404 { 405 flush(); 406 HipRenderer.setRendererMode(HipRendererMode.triangles); 407 } 408 rectangleVertices(x,y,w,h); 409 setColor(oldColor); 410 } 411 412 void draw() 413 { 414 const uint count = this.currentIndex; 415 import hip.console.log; 416 417 if(count - lastIndexDrawn != 0) 418 { 419 mesh.bind(); 420 mesh.updateVertices(cast(float[])vertices[lastVertexDrawn..verticesCount], lastVertexDrawn); 421 mesh.updateIndices(indices[lastIndexDrawn..currentIndex], lastIndexDrawn); 422 423 mesh.shader.setFragmentVar("FragVars.uGlobalColor", cast(float[4])[1,1,1,1], true); 424 mesh.shader.setVertexVar("Geom.uMVP", camera.getMVP, true); 425 426 mesh.shader.sendVars(); 427 //Vertices to render = indices.length 428 this.mesh.draw(count - lastIndexDrawn, HipRenderer.getMode, lastIndexDrawn); 429 mesh.unbind(); 430 } 431 lastIndexDrawn = count; 432 lastVertexDrawn = verticesCount; 433 } 434 435 void flush() 436 { 437 draw(); 438 lastVertexDrawn = verticesCount = 0; 439 lastIndexDrawn = currentIndex = 0; 440 } 441 442 }